\section{库介绍}
程序编译一般需要经预处理、编译、汇编和链接几个步骤。在实际应用中，有些公共代码需要反复使用，就把这些代码编译成为“库”文件。在链接步骤中，连接器将从库文件取得所需的代码，复制到生成的可执行文件中，这种库称为静态（链接）库，其特点是可执行文件中包含了库代码的一份完整拷贝，缺点是被多次使用就会多份冗余拷贝。还有一种库，就是程序在开始运行后调用库函数时才被载入，这种库独立于现有的程序，其本身不可执行，但包含着程序需要调用的一些函数，这种库称为动态（链接）库（Dynamic Link Library）。
\begin{figure}[H]
    \centering
    \includegraphics[width=0.5\textwidth]{so_1.pdf}
    \caption{库文件}
\end{figure}
在widows平台下，静态链接库是.lib文件，动态库文件是.dll文件。在linux平台下，静态链接库是.a文件，动态链接库是.so文件。这里主要讲在linux平台下的动态库和静态库的生成以及链接。
\subsection{库基础}
首先说明要对库有一个比较直观的理解。库是写好的现有的、成熟的、可以复用的代码。现实中每个程序都依赖很多基础的底层库，不可能每个人的代码都从零开始，因此库的存在意义非同寻常。本质上库是一种可执行代码的二进制形式（其本身不可执行），可以被操作系统载入内存执行。

静态链接库，之所以称为“静态库”，是因为在链接阶段，会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中，因此对应的链接方式为静态链接。其实一个静态链接库可以简单看成一组目标文件(.o/.obj文件)的集合，即很多目标文件经过压缩打包后形成的一个文件。静态库特点：
\begin{enumerate}
    \item 静态库对函数库的链接是放在编译时期完成
    \item 程序在运行时对函数库再无瓜葛。
    \item 浪费空间和资源，因为所有相关的目标文件和牵涉到的函数库被链接合成一个可执行文件。
\end{enumerate}
linux下使用ar工具（windows下用lib.exe），可以将目标文件压缩到一起，并且对其进行编号和索引，一便于查找和索引。一般创建静态链接库的步骤如下：
\begin{figure}[H]
    \centering
    \includegraphics[width=0.5\textwidth]{so_2.pdf}
    \caption{静态库创建}
\end{figure}
\begin{itemize}
    \item 静态链接库的命名规则和动态库类似，假定库名称为"my\_library\_name"，那么起库文件名为"lib[my\_library\_name].a"(方括号是为了区分，实际上没有)
    \item 动态链接库，在程序编译时并不会被连接到目标代码中，而是在程序运行时才被载入。不同的应用程序如果调用相同的库，那么在内存里只需要有一份该共享库的实例，规避了空间浪费问题。
\end{itemize}
动态库说明：
\begin{itemize}
    \item 动态库把对一些库函数的链接载入推迟到程序运行时期。
    \item 可以实现进程之间的资源共享，（动态库也成为共享库）。
    \item 将一些程序升级变得简单。
    \item 设置可以真正做到链接载入完全由程序员在程序代码中控制（显式调用）。
\end{itemize}
Linux下gcc编译的执行文件默认是ELF格式，不需要初始化入口，亦不需要函数做特别的声明，编写比较方便，与windows系统下的格式不同。与创建静态库不同的是，不需要打包工具，直接使用编译器即可创建动态库。
\begin{figure}[H]
    \centering
    \includegraphics[width=0.5\textwidth]{so_3.pdf}
    \caption{动态库（只在内存中存在一份）}
\end{figure}
动态链接库的命名规则，与静态链接库的方式相同，不过其后缀名为.so，命名形式为"lib[my\_library\_name].so"   。但是在实际使用过程中libxxx.so 大多数情况只是一个链接，它链接到一个包含版本信息的库文件 libxxxx.so.xx。当然自己可以使用 ln 命令，制作链接 ln -s libxxxx.so.xx libxxxx.so。  gcc/g++的编译参数，这里只介绍 -L 、-l、-include、-I、-shared、-fPIC
\begin{itemize}
    \item -L：表示要链接的库所在的目录。-L.  表示要链接的库在当前目录， -L/usr/lib 表示要连接的库在/usr/lib下。目录在/usr/lib时，系统会自动搜索这个目录，可以不用指明。
    \item -l (L的小写)：表示需要链接库的名称，注意不是库文件名称，比如库文件为 libtest.so，那么库名称为test
    \item -include ：包含头文件，这个很少用，因为一般情况下在源码中，都有指定头文件。
    \item -I (i 的大写)：指定头文件的所在的目录，可以使用相对路径。
    \item -shared ：指定生成动态链接库。
    \item -fPIC：  表示编译为位置独立的代码，不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要，而不能达到真正代码共享的目的。
\end{itemize}
\begin{example}[实现动态库使用]
    生成加法函数的动态库实现输入两个整数计算输出和。main函数调用add动态库实现用户输入两个数用add求和。
\end{example}
add.h文件
\cppfile{code/cmake/ex6/add.h}
add.cpp文件
\cppfile{code/cmake/ex6/add.cpp}
主函数
\cppfile{code/cmake/ex6/main.cpp}
\begin{enumerate}
    \item 通过add.cpp文件生成动态库。在当前目录下输入：\\*
          \bashinline{clang++ add.cpp -fPIC -shared -o libadd.so}
    \item 将动态库连接到主函数。\bashinline{clang++ main.cpp -L. -ladd -o main}
    \item 查看可执行程序main是否已经链接到libadd.so。
          \begin{bash}
              linux-vdso.so.1 =>  (0x00007fff78ff4000)
              libadd.so (0x00007fdee9cea000)
              libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fdee9968000)
              libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdee965f000)
              libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fdee9449000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdee907f000)
              /lib64/ld-linux-x86-64.so.2 (0x00007fdee9eec000)

          \end{bash}
\end{enumerate}
\section{CMake入门}

\subsection*{简单例子}
首先，在cmakecode目录建立一个 cmake\_code目录，用来放置我们学习过程中的所有练习。以后我们所有的 cmake 练习都会放在cmake\_code 的子目录下(你也可以自行安排目录，这个并不是限制，仅仅是为了叙述的方便)。然后在 cmake 建立第一个练习目录ex1，在ex1目录建立 test1.cc 和 CMakeLists.txt(注意文件名大小写)，
main.cpp 文件内容:
\cppfile{code/cmake/ex2/test1.cpp}
CMakeLists.txt 文件内容:
\cmakefile{code/cmake/ex2/CMakeLists.txt}
所有的文件创建完成后ex1目录中应该存在 test1.cpp 和 CMakeLists.txt 两个文件接下来我们来构建这个工程,新建一个build目录将输出文件放到build中，然后编译
\begin{bash}
    mkdir build
    cd build
    cmake ..
\end{bash}
输出大概是这个样子：
\begin{bash}
    -- The C compiler identification is GNU 5.4.0
    -- The CXX compiler identification is GNU 5.4.0
    -- Check for working C compiler: /usr/bin/cc
    -- Check for working C compiler: /usr/bin/cc -- works
    -- Detecting C compiler ABI info
    -- Detecting C compiler ABI info - done
    -- Detecting C compile features
    -- Detecting C compile features - done
    -- Check for working CXX compiler: /usr/bin/c++
    -- Check for working CXX compiler: /usr/bin/c++ -- works
    -- Detecting CXX compiler ABI info
    -- Detecting CXX compiler ABI info - done
    -- Detecting CXX compile features
    -- Detecting CXX compile features - done
    -- Configuring done
    -- Generating done
    -- Build files have been written to: /home/liushuai/cxxcode/code/cmake/ex2/build
\end{bash}
再让我们看一下目录中的内容，你会发现,系统自动生成了:\\*
\Textinline{CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile}
等文件,其中生成的Makefile文件能用于编译生成程序。然后进行工程的实际构建,在这个目录输入 make 命令,大概会得到如下的输出:
\begin{bash}
    [ 50% ] Building CXX object CMakeFiles/ex2.dir/test1.cpp.o
    [100%] Linking CXX executable ex2
    [100%] Built target ex2
\end{bash}
如果你需要看到 make 构建的详细过程,可以使用 make VERBOSE=1 或者 VERBOSE=1
make 命令来进行构建。这时候,我们需要的目标文件 ex2 已经构建完成,位于当前目录,尝试运行一下:
\bashinline{./ex2}。

\textbf{简单的解释}:
我们来重新看一下 CMakeLists.txt,这个文件是 cmake 的构建定义文件,文件名是大小写相关的,如果工程存在多个目录,需要确保每个要管理的目录都存在一个CMakeLists.txt。(关于多目录构建,后面我们会提到,这里不作过多解释)。
上面例子中的 CMakeLists.txt 文件内容如下:
\cmakefile{code/cmake/ex2/CMakeLists.txt}
\subsubsection{CMake常用指令}
\begin{itemize}
    \item PROJECT（CMake不区分大小写，指令名称一般是大写，这里为了方便CMakeList.txt文件中均使用小写） 指令的语法是:
          \Textinline{PROJECT(projectname [CXX] [C] [Java])}
          你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。这个指令隐式的定义了两个 cmake 变量：
          \begin{itemize}
              \item \Textinline{<projectname>_BINARY_DIR}
              \item \Textinline{<projectname>_SOURCE_DIR}
          \end{itemize}
          这里就是\Textinline{ex1_BINARY_DIR} 和 \Textinline{ex1_SOURCE_DIR}
          (所以 CMakeLists.txt 中两个 MESSAGE指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路径\bashinline{/home/liushuai/cxxcode/code/cmake/},后面我们会讲到外部编译,两者所指代的内容会有所不同。
          同时 cmake 系统也帮助我们预定义了：
          \begin{itemize}
              \item \Textinline{PROJECT_BINARY_DIR}
              \item \Textinline{PROJECT_SOURCE_DIR}
          \end{itemize}
          变量,它们的值分别跟 \Textinline{ex1_BINARY_DIR} 与 \Textinline{ex1_SOURCE_DIR} 一致。
          为了统一起见，建议以后直接使用 \Textinline{PROJECT_BINARY_DIR},\Textinline{PROJECT_SOURCE_DIR}，即使修改了工程名称，也不会影响这两个变量。如果使用了
          \Textinline{<projectname>_SOURCE_DIR} ，修改工程名称后,需要同时修改这些变量。
    \item SET 指令的语法是:

          \cmakeinline{SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])}\\*
          现阶段,你只需要了解 SET 指令可以用来显式的定义变量即可。
          比如我们用到的是:

          \cmakeinline{SET(SRC_LIST test1.cc)}\\*
          如果有多个源文件,也可以定义成:
          \cmakeinline{SET(SRC_LIST test1.cc t1.c t2.c)}。

    \item MESSAGE 指令的语法是:\\
          \cmakeinline{MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)}
          这个指令用于向终端输出用户定义的信息,包含了三种类型:
          \begin{itemize}
              \item SEND\_ERROR：产生错误,生成过程被跳过。
              \item SATUS ：输出前缀为 — 的信息。
              \item FATAL\_ERROR：立即终止所有 cmake 过程.我们在这里
                    使用的是 STATUS 信息输出,演示了由 PROJECT 指令定义的两个隐式变量
                    \Textinline{ex1_BINARY_DIR} 和\Textinline{ex1_SOURCE_DIR。}
          \end{itemize}

          \cmakeinline{ADD_EXECUTABLE(t ${SRC_LIST})}
          定义了这个工程会生成一个文件名为 t 的可执行文件,相关的源文件是\Textinline{SRC_LIST} 中定义的源文件列表, 本例中你也可以直接写成：\\*
          \cmakeinline{ADD_EXECUTABLE(t test1.cc)}。
          在本例我们使用了\$\{\}来引用变量,这是 cmake 的变量引用方式,但是,
          有一些例外,比
          如在 IF 控制语句,变量是直接使用变量名引用,而不需要\$\{\}。如果使用了\$\{\}
          去引用变量,其实 IF 会去判断名为\$\{\}所代表的值的变量,
          那当然是不存在的了。
          将本例改写成一个最简化的 CMakeLists.txt:
          \begin{cmake}
              PROJECT(ex1)
              ADD_EXECUTABLE(hello test1.cc)
          \end{cmake}
\end{itemize}
\subsection*{基本语法规则}
前面提到过cmake 其实仍然要使用 “cmake 语言和语法”去构建,上面的内容就是所谓的“cmake 语言和语法” ,最简单的语法规则是:
\begin{enumerate}
    \item 变量使用\$\{\}方式取值,但是在 IF 控制语句中是直接使用变量名
    \item 指令(参数 1 参数 2...)参数使用括号括起,参数之间使用空格或分号分开。以上面的 ADD\_EXECUTABLE 指令为例,如果存在另外一个 func.cc 源文件,就要写成:\\
          \cmakeinline{ADD_EXECUTABLE(t test1.cc func.cc)}或者：\\
          \cmakeinline{ADD_EXECUTABLE(t test1.cc;func.cc)}
    \item 指令是大小写无关的,参数和变量是大小写相关的。但通常推荐你全部使用大写指令。上面的 MESSAGE 指令我们已经用到了这条规则：
          \mint{cmake}{MESSAGE(STATUS “This is BINARY dir” ${ex1_BINARY_DIR})}也可以写成:
          \mint{cmake}{MESSAGE(STATUS “This is BINARY dir ${ex1_BINARY_DIR}”)}
          \begin{remark}
              这里需要特别解释的是作为工程名的 ex2 和生成的可执行文件 ex2 是没有任何关系的。ex2 定义了可执行文件的文件名,你完全可以写成：\mint{cmake}{ADD_EXECUTABLE(t test1.cc)}编译后会生成一个 t 可执行文件。
          \end{remark}
    \item 关于语法的疑惑,cmake 的语法还是比较灵活而且考虑到各种情况,比如：
          \mint{cmake}{SET(SRC_LIST test1.cc)}也可以写成:
          \mint{cmake}{SET(SRC_LIST “test1.cc”)}
          是没有区别的,但是假设一个源文件的文件名是 \bashinline{fu c.cc}(文件名中间包含了空格)。这时候就必须使用双引号,如果写成了：
          \mint{cmake}{SET(SRC_LIST fu nc.c)},就会出现错误,提示
          你找不到 fu 文件和 nc.c 文件。这种情况,就必须写成:
          \mint{cmake}{SET(SRC_LIST “fu nc.c”)}
          此外,你可以可以忽略掉 source 列表中的源文件后缀,比如可以写成：
          \mint{cmake}{ADD_EXECUTABLE(t test1)}cmake 会自动的在本目录查找 test1.cc 或者test1.cpp等,当然,最好不要偷这个懒,以免这个目录确实存在一个 test1.cc 一个 test1。同时参数也可以使用分号来进行分割。下面的例子也是合法的:
          \mint{cmake}{ADD_EXECUTABLE(t1 test1.cc t1.cc)}可以写成：
          \mint{cmake}{ADD_EXECUTABLE(t1 test1.cc;t1.cc)}
          我们只需要在编写 CMakeLists.txt 时注意形成统一的风格即可。
    \item 清理工程:跟经典的 autotools 系列工具一样,运行:
          \bashinline{make clean}即可对构建结果进行清理。
\end{enumerate}
\subsection{cmake常用变量和环境变量}
\begin{enumerate}
    \item cmake 变量引用的方式:
          前面我们已经提到了,使用\$\{\}进行变量的引用。在 IF 等语句中,是直接使用变量名而不
          通过\$\{\}取值

    \item cmake 自定义变量的方式：
          主要有隐式定义和显式定义两种,前面举了一个隐式定义的例子,就是 PROJECT 指令,它会隐式的定义<projectname>\_BINARY\_DIR 和<projectname>\_SOURCE\_DIR 两个变量。
          显式定义的例子我们前面也提到了,使用 SET 指令,就可以构建一个自定义变量了。
          比如：\mint{cmake}{SET(add_SRC main.SOURCE_PATH)}PROJECT\_BINARY\_DIR 可以通过\$\{add\_SRC\}来引用这个自定义变量了。
    \item cmake 常用变量
          \begin{enumerate}
              \item \begin{itemize}
                        \item \bashinline{CMAKE_SOURCE_DIR}
                        \item \bashinline{PROJECT_SOURCE_DIR}
                        \item \bashinline{<projectname>_SOURCE_DIR}
                    \end{itemize}
                    这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在 in source 编译时,它跟 \bashinline{CMAKE_BINARY_DIR} 等变量一致。\bashinline{PROJECT_SOURCE_DIR} 跟其它指令稍有区别,现在,你可以理解为它们是一致的。
              \item \bashinline{CMAKE_CURRENT_SOURCE_DIR}指的是当前处理的 CMakeLists.txt 所在的路径,比如上面我们提到的 src 子目录。
              \item \bashinline{CMAKE_CURRRENT_BINARY_DIR}：如果是 in-source 编译,它跟\bashinline{CMAKE_CURRENT_SOURCE_DIR}  一致,如果是 out-of-source 编译,它指的是 target 编译目录。使用我们上面提到的 ADD\_SUBDIRECTORY(src bin)可以更改这个变量的值。使用\mint{bash}{ SET(EXECUTABLE_OUTPUT_PATH <新路径>)}并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。
              \item \bashinline{CMAKE_CURRENT_LIST_FILE}：输出调用这个变量的 CMakeLists.txt 的完整路径。
              \item \bashinline{CMAKE_CURRENT_LIST_LINE}：输出这个变量所在的行。
              \item \bashinline{CMAKE_MODULE_PATH}：这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下。比如:
                    \mint{cmake}{SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)} 这时候你就可以通过 INCLUDE 指令来调用自己的模块了。
              \item \bashinline{EXECUTABLE_OUTPUT_PATH} 和 \bashinline{LIBRARY_OUTPUT_PATH}分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量。
              \item \Textinline{PROJECT_NAME}：返回通过 PROJECT 指令定义的项目名称。
              \item cmake 调用环境变量的方式使用\$ENV\{NAME\}指令就可以调用系统的环境变量了。比如：\mint{cmake}{MESSAGE(STATUS “HOME dir: $ENV{HOME}”)}
          \end{enumerate}
          设置环境变量的方式是:SET(ENV{变量名} 值)。
          \begin{enumerate}
              \item \bashinline{CMAKE_INCLUDE_CURRENT_DIR}：自动添加 \mint{bash}{CMAKE_CURRENT_BINARY_DIR} 和 \mint{bash}{CMAKE_CURRENT_SOURCE_DIR} 到当前处理的 CMakeLists.txt。相当于在每个 CMakeLists.txt 加入:
                    \begin{cmake}
                        INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
                    \end{cmake}
              \item \bashinline{CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE}
                    将工程提供的头文件目录始终置于系统头文件目录的前面,当你定义的头文件确实跟系统发生冲突时可以提供一些帮助。
              \item \bashinline{CMAKE_INCLUDE_PATH} 和 \bashinline{CMAKE_LIBRARY_PATH} 我们在上一节已经提及。
          \end{enumerate}
    \item 系统信息
          \begin{enumerate}
              \item \bashinline{CMAKE_MAJOR_VERSION,CMAKE}：主版本号,比如 2.4.6 中的 2。
              \item \bashinline{CMAKE_MINOR_VERSION,CMAKE}：次版本号,比如 2.4.6 中的 4。
              \item \bashinline{CMAKE_PATCH_VERSION,CMAKE}：补丁等级,比如 2.4.6 中的 6。
              \item \bashinline{CMAKE_SYSTEM}：系统名称,比如 Linux-2.6.22。
              \item \bashinline{CMAKE_SYSTEM_NAME}：不包含版本的系统名,比如 Linux。
              \item \bashinline{CMAKE_SYSTEM_VERSION}：系统版本,比如 2.6.22。
              \item \bashinline{CMAKE_SYSTEM_PROCESSOR}：处理器名称,比如 i686。
              \item \bashinline{UNIX}：在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin。
              \item \bashinline{WIN32}：在所有的 win32 平台为 TRUE,包括 cygwin。
          \end{enumerate}
    \item 主要的开关选项:
          \begin{itemize}
              \item \bashinline{CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS}：控制 IF ELSE 语句的书写方式。
              \item \bashinline{BUILD_SHARED_LIBS}：这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD\_LIBRARY 并没有指定库类型的情况下,默认编译生成的库都是静态库。如果 SET(BUILD\_SHARED\_LIBS ON)后,默认生成的为动态库。
              \item \bashinline{CMAKE_C_FLAGS}：设置 C 编译选项,也可以通过指令 ADD\_DEFINITIONS()添加。
              \item \bashinline{CMAKE_CXX_FLAGS}：设置 C++编译选项,也可以通过指令 ADD\_DEFINITIONS()添加。
          \end{itemize}
\end{enumerate}
\subsection{基本指令}
前面我们讲到了 cmake 常用的变量,相信 “ cmake 即编程 ” 的感觉会越来越明显,无论如何,我们仍然可以看到 cmake 比 autotools 要简单很多。接下来我们就要集中的看一看cmake 所提供的常用指令。在前面的章节我们已经讨论了很多指令的用法,如
\begin{itemize}
    \item \Textinline{PROJECT}
    \item \Textinline{ADD_EXECUTABLE}
    \item \Textinline{INSTALL}
    \item \Textinline{ADD_SUBDIRECTORY}
    \item \Textinline{SUBDIRS}
    \item \Textinline{INCLUDE_DIRECTORIES}
    \item \Textinline{LINK_DIRECTORIES}
    \item \Textinline{TARGET_LINK_LIBRARIES}
    \item \Textinline{SET}
\end{itemize}
本节会引入更多的 cmake 指令,为了编写的方便,我们将按照 cmake man page 的顺序来介绍各种指令,不再推荐使用的指令将不再介绍。
\begin{enumerate}
    \item ADD\_DEFINITIONS
          向 C/C++编译器添加-D 定义,比如：
          \mint{cmake}{ADD_DEFINITIONS( -DENABLE_DEBUG-DABC )}
          参数之间用空格分割。如果你的代码中定义了\mint{cpp}{#ifdef ENABLE_DEBUG #endif}这个代码块就会生效。如果要添加其它的编译器开关,可以通过\bashinline{CMAKE_C_FLAGS}变量\bashinline{CMAKE_CXX_FLAGS}变量设置。
    \item ADD\_DEPENDENCIES定义 target 依赖的其它 target,确保在编译本 target 之前,其它的 target 已经被构建。
          ADD\_DEPENDENCIES(target-name depend-target1 depend-target2 ...)
    \item ADD\_EXECUTABLE、ADD\_LIBRARY、ADD\_SUBDIRECTORY 前面已经介绍过了。
    \item ADD\_TEST 与 ENABLE\_TESTING 指令。ENABLE\_TESTING 指令用来控制 Makefile 是否构建 test 目标,涉及工程所有目录。语法很简单,没有任何参数\mint{Text}{ENABLE_TESTING()}一般情况这个指令放在工程的主CMakeLists.txt 中。ADD\_TEST 指令的语法是:\mint{Text}{ADD_TEST(testname Exename arg1 arg2 ...)} testname是自定义的 test 名称,Exename 可以是构建的目标文件也可以是外部脚本等等。后面连接传递给可执行文件的参数。如果没有在同一个 CMakeLists.txt 中打开 ENABLE\_TESTING() 指令,任何 ADD\_TEST 都是无效的。比如我们前面的 add 例子,可以在工程主 CMakeLists.txt 中添加：\mint{Text}{ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)}
          ENABLE\_TESTING()生成 Makefile 后,就可以运行 make test 来执行测试了。
    \item \bashinline{AUX_SOURCE_DIRECTORY}
          基本语法是:\mint{cmake}{AUX_SOURCE_DIRECTORY(dir VARIABLE)}

          作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前 cmake 还不能自动发现新添加的源文件。
          \begin{cmake}
              AUX_SOURCE_DIRECTORY(. SRC_LIST)
              ADD_EXECUTABLE(main ${SRC_LIST})
          \end{cmake}
          你也可以通过后面提到的 FOREACH 指令来处理这个 LIST
    \item \bashinline{CMAKE_MINIMUM_REQUIRED}其语法为： \mint{cmake}{CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])}
          比如 \mint{cmake}{CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)}如果 cmake 版本小与 2.5,则出现严重错误,整个过程中止。
    \item \bashinline{EXEC_PROGRAM}在 CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行。具体语法为:
          \begin{cmake}
              EXEC_PROGRAM(Executable [directory in which to run]
                  [ARGS <arguments to executable>]
                  [OUTPUT_VARIABLE <var>]
                  [RETURN_VALUE <var>])
          \end{cmake}
          用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过OUTPUT\_VARIABLE 和 RETURN\_VALUE 分别定义两个变量.这个指令可以帮助你在 CMakeLists.txt 处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。举个简单的例子,我们要在 src 目录执行 ls 命令,并把结果和返回值存下来。可以直接在 src/CMakeLists.txt 中添加:
          \begin{cmake}
              EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE
              LS_RVALUE)
              IF(not LS_RVALUE)
              MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
              ENDIF(not LS_RVALUE)
          \end{cmake}
          在 cmake 生成 Makefile 的过程中,就会执行 ls 命令,如果返回 0,则说明成功执行,那么就输出 ls *.c 的结果。关于 IF 语句,后面的控制指令会提到。
    \item FILE 指令
          文件操作指令,基本语法为:
          \begin{bash}
              FILE(WRITE filename "message to write"... )
              FILE(APPEND filename "message to write"... )
              FILE(READ filename variable)
              FILE(GLOB expressions]...)
              variable [RELATIVE path] [globbing
              FILE(GLOB_RECURSE variable [RELATIVE path]
                  [globbing expressions]...)
              FILE(REMOVE [directory]...)
              FILE(REMOVE_RECURSE [directory]...)
              FILE(MAKE_DIRECTORY [directory]...)
              FILE(RELATIVE_PATH variable directory file)
              FILE(TO_CMAKE_PATH path result)
              FILE(TO_NATIVE_PATH path result)
          \end{bash}
          这里的语法都比较简单,不在展开介绍了。
    \item INCLUDE 指令,用来载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块.
          \begin{cmake}
              INCLUDE(file1 [OPTIONAL])
              INCLUDE(module [OPTIONAL])
          \end{cmake}
          OPTIONAL 参数的作用是文件不存在也不会产生错误。你可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE\_MODULE\_PATH 中搜索这个模块并载入。载入的内容将在处理到 INCLUDE 语句是直接执行。
\end{enumerate}
\textbf{FIND\_指令}
FIND\_系列指令主要包含：
\begin{itemize}
    \item 查找文件指令\cmakeinline{FIND_FILE(<VAR> name1 path1 path2 ...)}：VAR 变量代表找到的文件全路径，包含文件名。
    \item \cmakeinline{FIND_LIBRARY(<VAR> name1 path1 path2 ...)}：VAR 变量表示找到的库全路径,包含库文件名。
    \item \cmakeinline{FIND_PATH(<VAR> name1 path1 path2 ...)}：VAR 变量代表包含这个文件的路径。
    \item \cmakeinline{FIND_PROGRAM(<VAR> name1 path1 path2 ...)}：VAR 变量代表包含这个程序的全路径。
    \item
          \begin{cmake}
              FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE][[REQUIRED|COMPONENTS] [componets...]])
          \end{cmake}
          用来调用预定义在\Textinline{CMAKE_MODULE_PATH} 下的 Find<name>.cmake 模块,你也可以自己定义 Find<name>模块,通过 \Textinline{SET(CMAKE_MODULE_PATH dir)}将其放入工程的某个目录中供工程使用
\end{itemize}
我们在后面的章节会详细介绍 FIND\_PACKAGE 的使用方法和 Find 模块的编写。

\textbf{FIND\_LIBRARY 示例}：
\begin{cmake}
    FIND_LIBRARY(libX X11 /usr/lib)
    IF(NOT libX)
    MESSAGE(FATAL_ERROR “libX not found”)
    ENDIF(NOT libX)
\end{cmake}

\subsubsection{控制指令}
\begin{enumerate}
    \item IF 指令,基本语法为:
          \begin{cmake}
              IF(expression)
              # THEN section.
              COMMAND1(ARGS ...)
              COMMAND2(ARGS ...)
              ELSE(expression)
              # ELSE section.
              COMMAND1(ARGS ...)
              COMMAND2(ARGS ...)
              ENDIF(expression)
          \end{cmake}
          另外一个指令是 ELSEIF,总体把握一个原则,凡是出现 IF 的地方一定要有对应的ENDIF.出现 ELSEIF 的地方,ENDIF 是可选的。表达式的使用方法如下:
          \begin{itemize}
              \item \cmakeinline{IF(var)}：如果变量不是空、0、N、 NO、 OFF、FALSE、NOTFOUND或<var>\_NOTFOUND 时,表达式为真。
              \item \cmakeinline{IF(NOT var )}：与上述条件相反。
              \item \cmakeinline{IF(var1 AND var2)}：当两个变量都为真是为真。
              \item \cmakeinline{IF(var1 OR var2)}：当两个变量其中一个为真时为真。
              \item \cmakeinline{IF(COMMAND cmd)}：当给定的 cmd 确实是命令并可以调用是为真。
              \item \cmakeinline{IF(EXISTS dir)}或者 \cmakeinline{IF(EXISTS file)}：当目录名或者文件名存在时为真。
              \item \cmakeinline{IF(file1 IS_NEWER_THAN file2)}：当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真,文件名请使用完整路径。
              \item \cmakeinline{IF(IS_DIRECTORY dirname)}：当 dirname 是目录时,为真。
              \item \cmakeinline{IF(variable MATCHES regex)}、\cmakeinline{IF(string MATCHES regex)}：当给定的变量或者字符串能够匹配正则表达式 regex 时为真。比如:
          \end{itemize}
          \begin{cmake}
              IF("add" MATCHES "ell")
              MESSAGE("true")
              ENDIF("add" MATCHES "ell")IF(variable LESS number)
              IF(string LESS number)
              IF(variable GREATER number)
              IF(string GREATER number)
              IF(variable EQUAL number)
              IF(string EQUAL number)
          \end{cmake}
          数字比较表达式
          \begin{cmake}
              IF(variable STRLESS string)
              IF(string STRLESS string)
              IF(variable STRGREATER string)
              IF(string STRGREATER string)
              IF(variable STREQUAL string)
              IF(string STREQUAL string)
          \end{cmake}
          按照字母序的排列进行比较,IF(DEFINED variable),如果变量被定义,为真。一个小例子,用来判断平台差异:
          \begin{cmake}
              IF(WIN32)
              MESSAGE(STATUS “This is windows.”)
              # 作一些 Windows 相关的操作
              ELSE(WIN32)
              MESSAGE(STATUS “This is not windows”)
              # 作一些非 Windows 相关的操作
              ENDIF(WIN32)
          \end{cmake}
          上述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服,ELSE(WIN32)之类的语句很容易引起歧义。这就用到了我们在 “ 常用变量 ” 一节提到的 \Textinline{CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS} 开关。可以:
          \mint{cmake}{SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)}
          这时候就可以写成:
          \begin{cmake}
              IF(WIN32)
              ELSE()
              ENDIF() #如果配合 ELSEIF 使用,可能的写法是这样:
              IF(WIN32)
              #do something related to WIN32
              ELSEIF(UNIX)
              #do something related to UNIX
              ELSEIF(APPLE)
              #do something related to APPLE
              ENDIF(WIN32)
          \end{cmake}
    \item WHILE 指令的语法是:
          \begin{cmake}
              WHILE(condition)
              COMMAND1(ARGS ...)
              COMMAND2(ARGS ...)
              ENDWHILE(condition)
          \end{cmake}
          其真假判断条件可以参考 IF 指令。

    \item FOREACH
          FOREACH 指令的使用方法有三种形式:
          \begin{enumerate}
              \item 列表
                    \begin{Textcode}
                        FOREACH(loop_var arg1 arg2 ...)
                        COMMAND1(ARGS ...)
                        COMMAND2(ARGS ...)
                        ...
                        ENDFOREACH(loop_var)
                    \end{Textcode}
                    像我们前面使用的 \bashinline{AUX_SOURCE_DIRECTORY} 的例子
                    \begin{cmake}
                        AUX_SOURCE_DIRECTORY(. SRC_LIST)
                        FOREACH(F ${SRC_LIST})
                            MESSAGE(${F})
                        ENDFOREACH(F)
                    \end{cmake}
              \item 范围
                    \begin{cmake}
                        FOREACH(loop_var RANGE total)
                        ENDFOREACH(loop_var)
                    \end{cmake}
                    从 0 到 total 以1为步进，举例如下:
                    \begin{cmake}
                        FOREACH(VAR RANGE 10)
                        MESSAGE(${VAR})
                    \end{cmake}
                    ENDFOREACH(VAR)
                    最终得到的输出是:
                    \begin{bash}
                        0
                        1
                        2
                        3
                        4
                        5
                        6
                        7
                        8
                        9
                        10
                    \end{bash}
              \item 范围和步进
                    \begin{bash}
                        FOREACH(loop_var RANGE start stop [step])
                        ENDFOREACH(loop_var)
                    \end{bash}
                    从 start 开始到 stop 结束,以 step 为步进,举例如下：
                    \begin{cmake}
                        FOREACH(A RANGE 5 15 3)
                        MESSAGE(${A})
                        ENDFOREACH(A)
                    \end{cmake}
                    最终得到的结果是:
                    \begin{bash}
                        5
                        8
                        11
                        14
                    \end{bash}
          \end{enumerate}
          这个指令需要注意的是,知道遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行。
\end{enumerate}
\textbf{小结:}\\
本小节基本涵盖了常用的 cmake 指令,包括基本指令、查找指令、安装指令以及控制语句等,特别需要注意的是,在控制语句条件中使用变量,不能用\$\{\}引用,而是直接引用变量名。掌握了以上的各种控制指令,你应该完全可以通过 cmake 管理复杂的程序了,下一节,我们将介绍一个比较复杂的例子,通过它来演示本章的一些指令,并介绍模块的概念。


\section{外部构建}
本小节的任务是创建一个完整的工程：
\begin{remark}
    \begin{enumerate}
        \item 为工程添加一个子目录 src,用来放置工程源代码。
        \item 添加一个子目录 doc,用来放置这个工程的文档 hello.txt。
        \item 在工程目录添加文本文件 COPYRIGHT, README。
        \item 在工程目录添加一个 runadd.sh 脚本,用来调用 hello 二进制。
        \item 将构建后的目标文件放入构建目录的 bin 子目录。
        \item 最终安装这些文件:将 add 二进制与 runadd.sh 安装至/usr/bin,
              将 doc 目录的内容以及 COPYRIGHT/README 安装到/usr/share/doc/cmake/t2。
    \end{enumerate}
\end{remark}
\subsubsection{准备工作:}
\begin{bash}
    .
    ├── bin
    ├── CMakeLists.txt
    ├── COPYRIGHT
    │   └── README.md
    ├── doc
    │   └── use.txt
    ├── runadd.sh
    └── src
    ├── Add.cpp
    ├── Add.h
    ├── CMakeLists.txt
    └── main.cpp

    4 directories, 8 files

\end{bash}

现在的工程看起来是这个样子:
一个子目录 src,一个 CMakeLists.txt。
上一节我们提到,需要为任何子目录建立一个 CMakeLists.txt,
进入子目录 src,编写 CMakeLists.txt 如下:
\cmakefile[firstline=2,lastline=2]{code/cmake/ex3/src/CMakeLists.txt}

将 ex3 工程的 CMakeLists.txt 修改为:
\cmakefile{code/cmake/ex3/CMakeLists.txt}
然后建立 build 目录,进入 build 目录进行外部编译。
\begin{bash}
    cmake ..
    make
\end{bash}
构建完成后,你会发现生成的目标文件 add 位于 build/bin 目录中。\\
\Textinline{ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])}
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE\_FROM\_ALL 参数的含义是将这个目录从编译过程中排除,比如,工程的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建(当然,你也可以通过定义依赖来解决此类问题)。

上面的例子定义了将 src 子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin 目录。如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在build/src 目录(这个目录跟原有的 src 目录对应),指定 bin 目录后,相当于在编译
将 src 重命名为 bin,所有的中间结果和目标二进制都将存放在 bin 目录。

\subsubsection{换个地方保存目标二进制}
ADD\_SUBDIRECTORY 指令(不论是否指定编译输出目录),我们都可以通过 SET 指令重新定义 EXECUTABLE\_OUTPUT\_PATH 和 LIBRARY\_OUTPUT\_PATH 变量
来指定最终的目标二进制的位置(指最终生成的 add 或者最终的共享库,不包含编译生成
的中间文件)
\begin{cmake}
    SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
        SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
\end{cmake}
在第一节我们提到了<projectname>\_BINARY\_DIR 和 PROJECT\_BINARY\_DIR 变量,他们指的编译发生的当前目录,如果是内部编译,就相当于 PROJECT\_SOURCE\_DIR 也就是工程代码所在目录,如果是外部编译,指的是外部编译所在目录,也就是本例中的 build
目录。所以,上面两个指令分别定义了:可执行二进制的输出路径为 build/bin 和库的输出路径为 build/lib。本节我们没有提到共享库和静态库的构建,所以,你可以不考虑第二条指令。问题是,我应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的
CMakeLists.txt,把握一个简单的原则,在哪里 ADD\_EXECUTABLE 或 ADD\_LIBRARY,就在哪里加入上述的定义。在这个例子里,当然就是指 src 下的 CMakeLists.txt 了。

\subsection{如何安装}
安装的需要有两种,一种是从代码编译后直接 make install 安装,一种是打包时的指定
目录安装。
所以,即使最简单的手工编写的 Makefile,看起来也是这个样子的:
\begin{bash}
    DESTDIR=
    install:
    mkdir -p $(DESTDIR)/usr/bin
        install -m 755 add $(DESTDIR)/usr/bin
\end{bash}
你可以通过:\bashinline{make install}
将 add 直接安装到\$(DESTDIR)/usr/bin 目录,也可以通过 make install DESTDIR=/tmp/test 将他安装在
/tmp/test/usr/bin 目录,打包时这个方式经常被使用。
稍微复杂一点的是还需要定义 PREFIX,一般 autotools 工程,会运行这样的指令:\\
\bashinline{./configure –prefix=/usr} 或者\\
\bashinline{./configure --prefix=/usr/local} 来指定
PREFIX比如上面的 Makefile 就可以改写成:
\begin{bash}
    DESTDIR=/
    PREFIX=usr
    install:
    mkdir -p $(DESTDIR)/$(PREFIX)/bin
    install -m 755 hello $(DESTDIR)/$(PREFIX)/bin
\end{bash}
那么我们的 add 应该怎么进行安装呢?
这里需要引入一个新的 cmake 指令 INSTALL 和一个非常有用的变量
\Textinline{CMAKE_INSTALL_PREFIX}。
CMAKE\_INSTALL\_PREFIX 变量类似于 configure 脚本的 – prefix,常见的使用方法看起来是这个样子:\\
\bashinline{cmake -DCMAKE_INSTALL_PREFIX=/usr .}\\
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及
文件、目录、脚本等。INSTALL 指令包含了各种安装类型,我们需要一个个分开解释:
\subsubsection{目标文件的安装}
\begin{cmake}
    INSTALL(TARGETS targets...
        [[ARCHIVE|LIBRARY|RUNTIME]
                [DESTINATION <dir>]
                [PERMISSIONS permissions...]
                [CONFIGURATIONS
                        [Debug|Release|...]]
                [COMPONENT <component>]
                [OPTIONAL]
        ] [...])
\end{cmake}
\begin{enumerate}
    \item  TARGETS 后面跟的就是我们通过 ADD\_EXECUTABLE 或者 ADD\_LIBRARY 定义的
          目标文件,可能是可执行二进制、动态库、静态库。目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME特指可执行目标二进制。
    \item DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候\\*
          CMAKE\_INSTALL\_PREFIX 其实就无效了。如果你希望使用：\\
          CMAKE\_INSTALL\_PREFIX 来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是：
\end{enumerate}
\bashinline{${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>}
举个简单的例子:
\begin{cmake}
    INSTALL(TARGETS myrun mylib mystaticlib
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION libstatic
    )
\end{cmake}
上面的例子会将:
可执行二进制 myrun 安装到：

\bashinline{${CMAKE_INSTALL_PREFIX}/bin}\\
目录动态库 libmylib 安装到\bashinline{${CMAKE_INSTALL_PREFIX}/lib} 目录
静态库 libmystaticlib 安装到
\bashinline{${CMAKE_INSTALL_PREFIX}/libstatic}\\
目录特别注意的是你不需要关心 TARGETS 具体生成的路径,只需要写上
TARGETS 名称就可以了。

\subsection{普通文件的安装}
\begin{cmake}
    INSTALL(FILES files... DESTINATION <dir>
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [RENAME <name>] [OPTIONAL])
\end{cmake}
可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义权限 PERMISSIONS,安装后的权限为:
OWNER\_WRITE, OWNER\_READ,
GROUP\_READ,和 WORLD\_READ,即 644 权限。
非目标文件的可执行程序安装(比如脚本之类):
\begin{cmake}
    INSTALL(PROGRAMS files... DESTINATION <dir>
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [RENAME <name>] [OPTIONAL])
\end{cmake}
跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为:
OWNER\_EXECUTE, GROUP\_EXECUTE, 和 WORLD\_EXECUTE,即 755 权限
目录的安装:
\begin{cmake}
    INSTALL(DIRECTORY dirs... DESTINATION <dir>
        [FILE_PERMISSIONS permissions...]
        [DIRECTORY_PERMISSIONS permissions...]
        [USE_SOURCE_PERMISSIONS]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [[PATTERN <pattern> | REGEX <regex>]
                [EXCLUDE] [PERMISSIONS permissions...]] [...])
\end{cmake}
这里主要介绍其中的 DIRECTORY、PATTERN 以及 PERMISSIONS 参数。
\begin{itemize}
    \item DIRECTORY 后面连接的是所在 Source 目录的相对路径,但务必须注意
          \begin{remark}
              abc 和 abc/有很大的区别。如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。
          \end{remark}
    \item PATTERN 用于使用正则表达式进行过滤,PERMISSIONS 用于指定 PATTERN 过滤后的文件权限。
\end{itemize}
我们来看一个例子:
\begin{cmake}
    INSTALL(DIRECTORY icons scripts/ DESTINATION share/myprojPATTERN "CVS" EXCLUDE
    PATTERN "scripts/*"
    PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
    GROUP_EXECUTE GROUP_READ)
\end{cmake}
这条指令的执行结果是:
将 icons 目录安装到 <prefix>/share/myproj,将 scripts/中的内容安装到
<prefix>/share/myproj
不包含目录名为 CVS 的目录,对于 scripts/* 文件指定权限为 OWNER\_EXECUTE
OWNER\_WRITE OWNER\_READ GROUP\_EXECUTE GROUP\_READ.
安装时 CMAKE 脚本的执行:\\*
\cmakeinline{INSTALL([[SCRIPT <file>] [CODE <code>]] [...])}
\begin{itemize}
    \item SCRIPT 参数用于在安装时调用 cmake 脚本文件(也就是<abc>.cmake 文件)
    \item CODE 参数用于执行 CMAKE 指令,必须以双引号括起来。比如:
\end{itemize}
\cmakeinline{INSTALL(CODE "MESSAGE(\"Sample install message.\")")}
安装还有几个被标记为过时的指令,比如 INSTALL\_FILES 等,这些指令已经不再推荐使用,所以,这里就不再赘述了。
下面,我们就来改写我们的工程文件,让他来支持各种文件的安装,并且,我们要使用CMAKE\_INSTALL\_PREFIX 指令。

\subsubsection{修改 add 支持安装}

下面改写各目录的 CMakeLists.txt 文件。
\begin{enumerate}
    \item 安装 COPYRIGHT/README,直接修改主工程文件 CMakelists.txt,加入以下指令:
          \cmakefile[firstline=3,lastline=3]{code/cmake/ex3/CMakeLists.txt}
    \item 安装 runhello.sh,直接修改主工程文件 CMakeLists.txt,加入如下指令:
          \cmakefile[firstline=4,lastline=4]{code/cmake/ex3/CMakeLists.txt}
    \item 安装 doc 中的 use.txt,这里有两种方式:
          \begin{enumerate}
              \item 通过在 doc 目录建立CMakeLists.txt 并将 doc 目录通过 ADD\_SUBDIRECTORY 加入工程来完成。
              \item 直接在工程目录通过INSTALL(DIRECTORY 来完成),前者比较简单,各位可以根据兴趣自己完成,我们来尝试后者,顺便演示以下 DIRECTORY 的安装。
          \end{enumerate}
          因为 use.txt 要安装到/<prefix>/share/doc/ex3,所以我们不能直接安装整个 doc 目录,这里采用的方式是安装 doc 目录中的内容,也就是使用"doc/"在工程文件中添加INSTALL(DIRECTORY doc/ DESTINATION share/doc/ex3)
    \item 尝试我们修改的结果:
          现在进入 build 目录进行外部编译,注意使用 CMAKE\_INSTALL\_PREFIX 参数,这里我们将它安装到了/tmp/ex3 目录:

          \bashinline{cmake -DCMAKE_INSTALL_PREFIX=/tmp/ex3 ..}
          结果如下：
          \begin{bash}
              -- The C compiler identification is GNU 5.4.0
              -- The CXX compiler identification is GNU 5.4.0
              -- Check for working C compiler: /usr/bin/cc
              -- Check for working C compiler: /usr/bin/cc -- works
              -- Detecting C compiler ABI info
              -- Detecting C compiler ABI info - done
              -- Detecting C compile features
              -- Detecting C compile features - done
              -- Check for working CXX compiler: /usr/bin/c++
              -- Check for working CXX compiler: /usr/bin/c++ -- works
              -- Detecting CXX compiler ABI info
              -- Detecting CXX compiler ABI info - done
              -- Detecting CXX compile features
              -- Detecting CXX compile features - done
              -- Configuring done
              -- Generating done
              -- Build files have been written to: /home/liushuai/cxxcode/code/cmake/ex3/COPYRIGHT
          \end{bash}
          然后运行
          \begin{bash}
              make
              make install
          \end{bash}
          如下：
          \begin{bash}
              [ 33%] Building CXX object src/CMakeFiles/add.dir/main.cpp.o
              [ 66%] Building CXX object src/CMakeFiles/add.dir/Add.cpp.o
              [100%] Linking CXX executable add
              [100%] Built target add
              Install the project...
              -- Install configuration: ""
              -- Installing: /tmp/ex3/share/doc/ex3/README
              -- Installing: /tmp/ex3/bin/runadd.sh
          \end{bash}
\end{enumerate}
让我们进入/tmp/ex3 目录看一下安装结果:
\begin{bash}
    /tmp/ex3
    ├── bin
    │   └── runadd.sh
    └── share
    └── doc
    └── ex3
    └── README
\end{bash}
如果你要直接安装到系统,可以使用如下指令:
\bashinline{cmake -DCMAKE_INSTALL_PREFIX=/usr ..}

如果我没有定义CMAKE\_INSTALL\_PREFIX 会安装到什么地方?
你可以尝试一下：
\begin{bash}
    cmake ..
    make
    make install
\end{bash}
你会发现
CMAKE\_INSTALL\_PREFIX 的默认定义是/usr/local

\subsubsection{小结}
\begin{remark}
    本小节主要描述了如何在工程中使用多目录、各种安装指令以及 CMAKE\_INSTALL\_PREFIX 变量在下一小节,我们将探讨如何在 cmake 中构建动态库和静态库,以及如何使用外部头文件和外部共享库,毕竟,这是程序编写中最长使用的。
\end{remark}
\section{静态库和动态库}
\textbf{本节的任务}
\begin{enumerate}
    \item 建立一个静态库和动态库供其它程序编程使用向终端输出两个数字的和。
    \item 安装头文件与共享库。
\end{enumerate}
\begin{enumerate}
    \item 准备工作在./ex4/ 目录建立 ex4 目录,用于存放本节涉及到的工程
    \item 建立共享库
          \begin{bash}
              cd ex4
              mkdir lib
          \end{bash}
          在 ex4 目录下建立 CMakeLists.txt,内容如下:
          \cmakefile{code/cmake/ex4/CMakeLists.txt}
          在 lib 目录下建立两个源文件 add.cpp 与 add.h。add.cpp 内容如下:
          \cppfile{code/cmake/ex4/lib/add.cpp}
          add.h 内容如下:
          \cppfile{code/cmake/ex4/lib/add.h}
          在 lib 目录下建立 CMakeLists.txt,内容如下:
          \cmakefile[firstline=1,lastline=2]{code/cmake/ex4/lib/CMakeLists.txt}

    \item 编译共享库：仍然采用 out-of-source 编译的方式,按照习惯,我们建立一个 build 目录,在 build
          目录中
          \begin{bash}
              cmake ..
              make
          \end{bash}
          这时,你就可以在 lib（build） 目录得到一个 libadd.so,这就是我们期望的共享库。如果你要指定 libadd.so 生成的位置,可以通过在主工程文件 CMakeLists.txt中修改ADD\_SUBDIRECTORY(lib)指令来指定一个编译输出位置或者在lib/CMakeLists.txt 中添加SET(LIBRARY\_OUTPUT\_PATH <路径>)来指定一个新的位置。这两者的区别我们上一节已经提到了,所以,这里不再赘述,下面,我们解释一下一个新的指令 ADD\_LIBRARY
          \begin{cmake}
              ADD_LIBRARY(libname
                  [SHARED|STATIC|MODULE]
                  [EXCLUDE_FROM_ALL]
              source1 source2 ... sourceN)
          \end{cmake}
          你不需要写全 libadd.so,只需要填写 add 即可,cmake 系统会自动为你生成
          libadd.X
          类型有三种:
          \begin{itemize}
              \item SHARED,动态库
              \item STATIC,静态库
              \item MODULE,在使用
                    dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
          \end{itemize}
          EXCLUDE\_FROM\_ALL 参数的意思是这个库不会被默认构建,除非有其它的组件依赖或者手
          工构建。

    \item 添加静态库:同样使用上面的指令,我们在支持动态库的基础上再为工程添加一个静态库,按照一般的习惯,静态库名字跟动态库名字应该是一致的,只不过后缀是.a 罢了。下面我们用这个指令再来添加静态库:
          如果我们把上面的 add\_static 生成的静态文件将为libadd\_static.a,如果希望生成的静态文件为libadd.a可以使用\\*SET\_TARGET\_PROPERTIES,具体修改如下：\\
          \cmakeinline{ADD_LIBRARY(add_static STATIC ${LIBADD_SRC})}
          就可以构建一个 libadd\_static.a 的静态库了。这种结果显示不是我们想要的,我们需要的是名字相同的静态库和动态库,因为 target 名称是唯一的,所以,我们肯定不能通过 ADD\_LIBRARY 指令来实现了。这时候我们需要用到另外一个指令:SET\_TARGET\_PROPERTIES,其基本语法是:
          \begin{cmake}
              SET_TARGET_PROPERTIES(target1 target2 ...
              PROPERTIES prop1 value1
              prop2 value2 ...)
          \end{cmake}
          这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本。
          在本例中,我们需要做的是向 lib/CMakeLists.txt 中添加一条:\\*
          \cmakeinline{SET_TARGET_PROPERTIES(add_static PROPERTIES OUTPUT_NAME "add")}
          这样,我们就可以同时得到 libadd.so/libadd.a 两个库了。
          与它对应的指令是:\\
          \Textinline{GET_TARGET_PROPERTY(VAR target property)}
          具体用法如下例,我们向 lib/CMakeListst.txt 中添加:
          \begin{cmake}
              GET_TARGET_PROPERTY(OUTPUT_VALUE add_static OUTPUT_NAME)
              MESSAGE(STATUS “This is the add_static
              OUTPUT_NAME:”${OUTPUT_VALUE})
          \end{cmake}
          如果没有这个属性定义,则返回 NOTFOUND。
          \begin{bash}
              -- Tis is the add_static OUTPUT_NAMEOUTPUT_VALUE-NOTFOUND
              -- Configuring done
              -- Generating done
              -- Build files have been written to: /home/liushuai/cxxcode/code/cmake/ex4/build
          \end{bash}
          让我们来检查一下最终的构建结果,我们发现,libadd.a 已经构建完成,位于
          build/lib 目录中,但是 libadd.so 去消失了。这个问题的原因是:\textcolor{red}{cmake 在构建一个新的 target 时,会尝试清理掉其它使用这个名字的库,因为,在构建 libadd.a 时,就会清理掉 libadd.so。}为了回避这个问题,比如再次使用 SET\_TARGET\_PROPERTIES 定义
          CLEAN\_DIRECT\_OUTPUT 属性。
          向 lib/CMakeLists.txt 中添加:
          \cmakefile[firstline=3,lastline=]{code/cmake/ex4/lib/CMakeLists.txt}
          这时候,我们再次进行构建,会发现 build/lib 目录中同时生成了 libadd.so 和libadd.a
    \item 动态库版本号按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是
          \begin{itemize}
              \item libadd.so.1.2
              \item libadd.so ->libadd.so.1
              \item libadd.so.1->libadd.so.1.2
          \end{itemize}
          为了实现动态库版本号,我们仍然需要使用 SET\_TARGET\_PROPERTIES 指令。
          具体使用方法如下:
          SET\_TARGET\_PROPERTIES(add PROPERTIES VERSION 1.2 SOVERSION 1)
          VERSION 指代动态库版本,SOVERSION 指代 API 版本。
          将上述指令加入 lib/CMakeLists.txt 中,重新构建看看结果。
          在 build/lib 目录会生成:
          \begin{itemize}
              \item libadd.so.1.2
              \item libadd.so.1->libadd.so.1.2
              \item libadd.so ->libadd.so.1
          \end{itemize}
    \item 安装共享库和头文件
          以上面的例子,我们需要将 libadd.a, libadd.so.x 以及 add.h 安装到系统目
          录,才能真正让其它人开发使用,在本例中我们将 add 的共享库安装到<prefix>/lib
          目录,将 add.h 安装到<prefix>/include/add 目录。
          利用上一节了解到的 INSTALL 指令,我们向 lib/CMakeLists.txt 中添加如下指令:
          INSTALL(TARGETS add add\_staticLIBRARY DESTINATION lib
          ARCHIVE DESTINATION lib)
          INSTALL(FILES add.h DESTINATION include/add)
          注意,静态库要使用 ARCHIVE 关键字
          通过:
          \begin{bash}
              cmake -DCMAKE_INSTALL_PREFIX=/usr ..
              make
              make install
          \end{bash}
\end{enumerate}
我们就可以将头文件和共享库安装到系统目录/usr/lib 和/usr/include/add 中了。
\subsubsection{小节}
本小节,我们谈到了:
\begin{itemize}
    \item 如何通过 ADD\_LIBRARY 指令构建动态库和静态库。
    \item 如何通过 SET\_TARGET\_PROPERTIES 同时构建同名的动态库和静态库。
    \item 如何通过 SET\_TARGET\_PROPERTIES 控制动态库版本
\end{itemize}
最终使用上一节谈到的 INSTALL 指令来安装头文件和动态、静态库。
在下一节,我们需要编写另一个高级一点的 add 来演示怎么使用我们已经构建的构建的共享库 libadd 和外部头文件。
\subsection{使用外部共享库和头文件}
上一节我们已经完成了 libadd 动态库的构建以及安装,本节我们的任务很简单:
编写一个程序使用我们上一节构建的共享库。
\begin{Textcode}
    .
    ├── CMakeLists.txt
    ├── lib
    │   ├── add.cpp
    │   ├── add.h
    │   ├── CMakeLists.txt
    │   └── libadd.so
    └── src
    ├── add.h
    ├── CMakeLists.txt
    └── main.cpp
\end{Textcode}
\begin{enumerate}
    \item 准备工作:请在./ex5 目录建立 ex5 目录,本节所有资源将存储在 ex5 目录。
    \item 重复以前的步骤,建立 src 目录,编写源文件 main.cpp,内容如下:
          \textcolor{red}{代码缺失}
          %\cppfile{code/cmake/ex5/main.cpp}
          编写工程主文件 CMakeLists.txt
          \begin{cmake}
              PROJECT(NEWHELLO)
              ADD_SUBDIRECTORY(src)
          \end{cmake}

          编写 src/CMakeLists.txt ADD\_EXECUTABLE(ex5 main.cpp)上述工作已经严格按照我们前面几节提到的内容完成了。
    \item 外部构建,按照习惯,仍然建立 build 目录,使用 cmake ..方式构建。
          过程:
          \begin{bash}
              cmake ..
              make
          \end{bash}
          构建失败,如果需要查看细节,可以使用第一节提到的方法make VERBOSE=1 来构建错误输出为是:
          \begin{bash}
              make -f src/CMakeFiles/ex5.dir/build.make src/CMakeFiles/ex5.dir/build
              make[2]: Entering directory '/home/liushuai/cxxcode/code/cmake/ex5/build'
              [ 50% ] Building CXX object src/CMakeFiles/ex5.dir/main.cpp.o                                           
              cd /home/liushuai/cxxcode/code/cmake/ex5/build/src && /usr/bin/c++     -o CMakeFiles/ex5.dir/main.cpp.o
              -c /home/liushuai/cxxcode/code/cmake/ex5/src/main.cpp
              /home/liushuai/cxxcode/code/cmake/ex5/src/main.cpp:2:17: fatal error: add.h: 没有那个文件或目录
              compilation terminated.

          \end{bash}
    \item 引入头文件搜索路径。
          add.h 位于ex5/lib/add.h 目录中,并没有位于系统标准的头文件路径, 为了让我们的工程能够找到 add.h 头文件,我们需要引入一个新的指令 INCLUDE\_DIRECTORIES,其完整语法为:\\
          \cmakeinline{INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)}\\
          这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,你可以通过两种方式来进行控制搜索路径添加的方式:
          \begin{enumerate}
              \item CMAKE\_INCLUDE\_DIRECTORIES\_BEFORE,通过 SET 这个 cmake 变量为 on,可以 将添加的头文件搜索路径放在已有路径的前面。
              \item 通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前。现在我们在 src/CMakeLists.txt 中添加一个头文件搜索路径,方式很简单,加入：
                    \mint{cmake}{INCLUDE_DIRECTORIES(/home/liuhsuai/cxxcode/cmake/ex5/lib)} 进入 build 目录,重新进行构建,这时找不到 add.h 的错误已经消失,但是出现了一个新的错误:
                    \begin{bash}
                        [ 50% ] Building CXX object src/CMakeFiles/ex5.dir/main.cpp.o             
                        [100%] Linking CXX executable ex5                                        
                        CMakeFiles/ex5.dir/main.cpp.o：在函数‘main’中：
                        main.cpp:(.text+0x45)：对‘add(int, int)’未定义的引用
                        collect2: error: ld returned 1 exit status
                        src/CMakeFiles/ex5.dir/build.make:83: recipe for target 'src/ex5' failed
                        make[2]: *** [src/ex5] Error 1
                    \end{bash}
                    因为我们并没有 link 到共享库 libadd 上。
          \end{enumerate}
    \item 为 target 添加共享库
          我们现在需要完成的任务是将目标文件链接到 libadd,这里我们需要引入两个新的指令
          \begin{itemize}
              \item LINK\_DIRECTORIES
              \item TARGET\_LINK\_LIBRARIES，
          \end{itemize}
          LINK\_DIRECTORIES 的全部语法是:
          \begin{itemize}
              \item[\S] \mint{cmake}{LINK_DIRECTORIES(directory1 directory2 ...)}
                  这个指令非常简单,添加非标准的共享库搜索路径,比如：在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。这个例子中我们没有用到这个指令。
              \item[\S] TARGET\_LINK\_LIBRARIES 的全部语法是:
                  \begin{cmake}
                      TARGET_LINK_LIBRARIES(target library1
                      <debug | optimized> library2
                      ...)
                  \end{cmake}
                  这个指令可以用来为 target 添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写的共享库添加共享库链接。为了解决我们前面遇到的 add 未定义错误,我们需要做的是向src/CMakeLists.txt 中添加如下指令：
                  \mint{cmake}{TARGET_LINK_LIBRARIES(ex5 add)}也可以写成：\mint{cmake}{TARGET_LINK_LIBRARIES(ex5 libadd.so)}
          \end{itemize}
          这里的 add 指的是我们上一节构建的共享库 libadd进入 build 目录重新进行构建：
          \begin{bash}
              -- The C compiler identification is GNU 5.4.0
              -- The CXX compiler identification is GNU 5.4.0
              -- Check for working C compiler: /usr/bin/cc
              -- Check for working C compiler: /usr/bin/cc -- works
              -- Detecting C compiler ABI info
              -- Detecting C compiler ABI info - done
              -- Detecting C compile features
              -- Detecting C compile features - done
              -- Check for working CXX compiler: /usr/bin/c++
              -- Check for working CXX compiler: /usr/bin/c++ -- works
              -- Detecting CXX compiler ABI info
              -- Detecting CXX compiler ABI info - done
              -- Detecting CXX compile features
              -- Detecting CXX compile features - done
              -- Configuring done
              -- Generating done
              -- Build files have been written to: /home/liushuai/cxxcode/code/cmake/ex5/build
          \end{bash}
          编译输出：
          \begin{bash}
              [ 50%] Building CXX object src/CMakeFiles/ex5.dir/main.cpp.o    
              [100%] Linking CXX executable ex5                               
              [100%] Built target ex5                                         
          \end{bash}
          这时我们就得到了一个连接到 libadd 的可执行程序 ex5,位于 build/src 目录,运行 src/ex5等待输入两个整数计算结果。让我们来检查一下 main 的链接情况:
          \begin{bash}
              ldd src/ex5
              linux-vdso.so.1 =>  (0x00007ffc0558f000)
              libadd.so => /home/liushuai/cxxcode/code/cmake/ex5/lib/libadd.so (0x00007f6f5fc3a000)
              libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f6f5f8b8000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6f5f4ee000)
              libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f6f5f1e5000)
              /lib64/ld-linux-x86-64.so.2 (0x00007f6f5fe3c000)
              libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f6f5efcf000)

          \end{bash}
          可以清楚的看到 ex5 确实链接了共享库 libadd,而且链接的是动态库libadd.so.1那如何链接到静态库呢?
          方法很简单:
          将 \mint{cmake}{TARGET_LINK_LIBRARIES(main libadd.so)} 指令修改为:\mint{cmake}{TARGET_LINK_LIBRARIES(main libadd.a)}重新构建后再来看一下 ex5 的链接情况
          \begin{bash}
              ldd src/main
              lld is a generic driver.
              Invoke ld.lld (Unix), ld (macOS) or lld-link (Windows) instead.
          \end{bash}
          说明ex5 确实链接到了静态库 libadd.a

          \begin{remark}
              特殊的环境变量 CMAKE\_INCLUDE\_PATH 和 CMAKE\_LIBRARY\_PATH务必注意,这两个是环境变量而不是 cmake 变量。使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者CMAKE\_INCLUDE\_PATH=/home/include cmake ..等方式。这两个变量主要是用来解决以前 autotools 工程中--extra-include-dir 等参数的支持的。也就是,如果头文件没有存放在常规路径(/usr/include, /usr/local/include 等),则可以通过这些变量就行弥补。
          \end{remark}
          我们以本例中的 add.h 为例,它存放在/usr/include/add 目录,所以直接查找肯定是找不到的。前面我们直接使用了绝对路径 \mint{cmake}{INCLUDE_DIRECTORIES(/usr/include/add)}告诉工程这个头文件目录。为了将程序更智能一点,我们可以使用 \mint{Text}{CMAKE_INCLUDE_PATH} 来进行,使用 bash 的方法如下:
          \bashinline{export CMAKE_INCLUDE_PATH=/usr/include/add}
          然后在头文件中将 INCLUDE\_DIRECTORIES(/usr/include/add)替换为:
          \begin{cmake}
              FIND_PATH(myHeader add.h)
              IF(myHeader)
              INCLUDE_DIRECTORIES(${myHeader})
              ENDIF(myHeader)
          \end{cmake}
          上述的一些指令我们在后面会介绍。
          这里简单说明一下,FIND\_PATH 用来在指定路径中搜索文件名,比如:
          \mint{cmake}{FIND_PATH(myHeader NAMES add.h PATHS /usr/include /usr/include/add)}
          这里我们没有指定路径,但是,cmake 仍然可以帮我们找到 add.h 存放的路径,就是因为我们设置了环境变量 CMAKE\_INCLUDE\_PATH。
          如果你不使用 \mint{bash}{FIND_PATH}\mint{bash}{CMAKE_INCLUDE_PATH} 变量的设置是没有作用的,你不能指望它会直接为编译器命令添加参数
          \mint{bash}{-I<CMAKE_INCLUDE_PATH>}
          以此为例,CMAKE\_LIBRARY\_PATH 可以用在 FIND\_LIBRARY 中。同样,因为这些变量直接为 FIND\_指令所使用,所以所有使用 FIND\_指令的 cmake 模块都会受益。
\end{enumerate}

本节我们探讨了:
\begin{itemize}
    \item 如何通过 INCLUDE\_DIRECTORIES 指令加入非标准的头文件搜索路径。
    \item 如何通过 LINK\_DIRECTORIES 指令加入非标准的库文件搜索路径。
    \item 如果通过 TARGET\_LINK\_LIBRARIES 为库或可执行二进制加入库链接。并解释了如果链接到静态库。
\end{itemize}
到这里,或许你可以理解前面讲到的 “ cmake 的使用过程其实就是学习 cmake 语言并编写cmake 程序的过程 ” ,既然是 “ cmake 语言 ” ,自然涉及到变量、语法等。
到这里为止,您应该基本可以使用 cmake 工作了,但是还有很多高级的话题没有探讨,比如编译条件检查、编译器定义、平台判断、如何跟 pkgconfig 配合使用等等。
\begin{itemize}
    \item \bashinline{link_directories(dir1 dir2,....)}：相当于clang++中的 -L， 主要用于将指定的动态链接库路径添加到工程搜索路径中。
    \item \bashinline{link_libraries(dir1 dir2,...)}:如果path是目录，cmake会自动搜索目录中的库文件，如果是库文件的完整路径，则直接将需要的库添加到工程中，目录或者文件可以有多个。
    \item \bashinline{target_link_libraries(pro libname)}:pro表示当前工程名称，libname是添加动态库的名称，如果你想添加add库，他的静态库名称通常是libadd.a,动态库名称为libadd.so,这里你可以指定两者任一或者直接指定add。
    \item \bashinline{FIND_LIBRARY(MY_LIB libmylib.a ./)}在当前路径\bashinline{./}搜索静态链接库libmylib.a保存在变量\bashinline{MY_LIB},使用这个库可以通过:\mint{bash}{TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${MY_LIB})}
\end{itemize}
下一节,我们将抛开程序的话题,看看常用的 CMAKE 变量以及一些基本的控制语法规则。
\section{模块和自定义模块}
本章我们将着重介绍系统预定义的 Find 模块的使用以及自己编写 Find 模块,系统中提供了其它各种模块,一般情况需要使用 INCLUDE 指令显式的调用,FIND\_PACKAGE 指令是一个特例,可以直接调用预定义的模块。其实使用纯粹依靠 cmake 本身提供的基本指令来管理工程是一件非常复杂的事情,所以,cmake 设计成了可扩展的架构,可以通过编写一些通用的模块来扩展 cmake。
在本章,我们准备首先介绍一下 cmake 提供的 FindCURL 模块的使用。然后,基于我们前面的 libadd 共享库,编写一个 Findadd.cmake 模块。
\begin{enumerate}
    \item 使用 FindCURL 模块
          在当前目录建立 ex7 目录,用于存放我们的 CURL 的例子。建立 src 目录,并建立 src/main.c,内容如下:
          \begin{cpp}
              #include <curl/curl.h>
              #include <stdio.h>
              #include <stdlib.h>
              #include <unistd.h>
              FILE *fp;
              int write_data(void *ptr, size_t size, size_t nmemb, void *stream)
              {
                      int written = fwrite(ptr, size, nmemb, (FILE *)fp);
                      return written;
                  }
              int main()
              {
                      const char * path = "/tmp/curl-test";
                      const char * mode = "w";
                      fp = fopen(path,mode);
                      curl_global_init(CURL_GLOBAL_ALL);
                      CURLcode res;
                      CURL *curl = curl_easy_init();
                      curl_easy_setopt(curl, CURLOPT_URL, "http://www.linux-ren.org");
                      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
                      curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
                      res = curl_easy_perform(curl);
                      curl_easy_cleanup(curl);
                  }
          \end{cpp}
          这段代码的作用是通过 curl 取回 www.linux-ren.org 的首页并写入/tmp/curl-test文件中。建立主工程文件 CMakeLists.txt
          \begin{cmake}
              PROJECT(CURLTEST)
              ADD_SUBDIRECTORY(src)# 建立 src/CMakeLists.txt
              ADD_EXECUTABLE(curltest main.c)
          \end{cmake}
          现在自然是没办法编译的,我们需要添加 curl 的头文件路径和库文件。
          \begin{enumerate}
              \item 方法一：直接通过 INCLUDE\_DIRECTORIES 和 TARGET\_LINK\_LIBRARIES 指令添加:我们可以直接在 src/CMakeLists.txt 中添加:
                    \begin{cmake}
                        INCLUDE_DIRECTORIES(/usr/include)
                        TARGET_LINK_LIBRARIES(curltest curl)
                    \end{cmake}
                    然后建立 build 目录进行外部构建即可。现在我们要探讨的是使用 cmake 提供的 FindCURL 模块。
              \item 方法二：

                    使用 FindCURL 模块。向 src/CMakeLists.txt 中添加:
                    \begin{cmake}
                        FIND_PACKAGE(CURL)
                        IF(CURL_FOUND)
                        INCLUDE_DIRECTORIES(\${CURL_INCLUDE_DIR})
                        TARGET_LINK_LIBRARIES(curltest \${CURL_LIBRARY})
                        ELSE(CURL_FOUND)
                        MESSAGE(FATAL_ERROR ”CURL library not found”)
                        ENDIF(CURL_FOUND)
                    \end{cmake}
          \end{enumerate}
          对于系统预定义的 Find<name>.cmake 模块,使用方法一般如上例所示:每一个模块都会定义以下几个变量
          \begin{itemize}
              \item \Textinline{<name>_FOUND}
              \item \Textinline{<name>_INCLUDE_DIR or <name>_INCLUDES}
              \item \Textinline{<name>_LIBRARY or <name>_LIBRARIES}
          \end{itemize}
          你可以通过<name>\_FOUND 来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。如果<name>\_FOUND 为真,则将<name>\_INCLUDE\_DIR 加入
          INCLUDE\_DIRECTORIES,
          将<name>\_LIBRARY 加入 TARGET\_LINK\_LIBRARIES 中。我们再来看一个复杂的例子,通过<name>\_FOUND 来控制工程特性:
          \begin{cmake}
              SET(mySources viewer.c)
              SET(optionalSources)SET(optionalLibs)
              FIND_PACKAGE(JPEG)
              IF(JPEG_FOUND)
              SET(optionalSources ${optionalSources} jpegview.c)
                  INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )
              SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} )
              ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
              ENDIF(JPEG_FOUND)
              IF(PNG_FOUND)
              SET(optionalSources ${optionalSources} pngview.c)
                  INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )
              SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} )
              ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
              ENDIF(PNG_FOUND)
              ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} )
              TARGET_LINK_LIBRARIES(viewer ${optionalLibs}
          \end{cmake}
          通过判断系统是否提供了 JPEG 库来决定程序是否支持 JPEG 功能。
    \item 编写属于自己的 Findadd 模块。
          我们在此前的 t3 实例中,演示了构建动态库、静态库的过程并进行了安装。接下来,我们在 t6 示例中演示如何自定义 Findadd 模块并使用这个模块构建工程：

          请在建立/backup/cmake/中建立 t6 目录,并在其中建立 cmake 目录用于存放我们自己定义的 Findadd.cmake 模块,同时建立 src 目录,用于存放我们的源文件。

          定义 cmake/Findadd.cmake 模块
          \begin{cmake}
              FIND_PATH(add_INCLUDE_DIR add.h /usr/include/add
              /usr/local/include/add)
              FIND_LIBRARY(add_LIBRARY NAMES add PATH /usr/lib
              /usr/local/lib)
              IF (add_INCLUDE_DIR AND add_LIBRARY)
              SET(add_FOUND TRUE)
              ENDIF (add_INCLUDE_DIR AND add_LIBRARY)
              IF (add_FOUND)
              IF (NOT add_FIND_QUIETLY)
              MESSAGE(STATUS "Found add: ${add_LIBRARY}")ENDIF (NOT add_FIND_QUIETLY)
              ELSE (add_FOUND)
              IF (add_FIND_REQUIRED)
              MESSAGE(FATAL_ERROR "Could not find add library")
              ENDIF (add_FIND_REQUIRED)
              ENDIF (add_FOUND)
          \end{cmake}
          针对上面的模块让我们再来回顾一下 FIND\_PACKAGE 指令:
          \begin{bash}
              FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
                  [[REQUIRED|COMPONENTS] [componets...]])
          \end{bash}
          前面的 CURL 例子中我们使用了最简单的 FIND\_PACKAGE 指令,其实它可以使用多种参数,QUIET 参数,对应与我们编写的 Findadd 中的 add\_FIND\_QUIETLY,如果不指定这个参数,就会执行:
          \Textinline{MESSAGE(STATUS "Found add: ${add_LIBRARY}")}
          REQUIRED 参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于Findadd.cmake 模块中的 add\_FIND\_REQUIRED 变量。同样,我们在上面的模块中定义了 \bashinline{add_FOUND},\bashinline{add_INCLUDE_DIR},\bashinline{add_LIBRARY} 变量供开发者在 \bashinline{FIND_PACKAGE} 指令中使用。OK,下面建立 src/main.c,内容为:
          \begin{cpp}
              #include <add.h>
              int main()
              {
                      addFunc();
                      return 0;
                  }
          \end{cpp}
          建立 src/CMakeLists.txt 文件,内容如下:
          \begin{cmake}
              FIND_PACKAGE(add)
              IF(add_FOUND)
              ADD_EXECUTABLE(add main.c)
              INCLUDE_DIRECTORIES(${add_INCLUDE_DIR})
                  TARGET_LINK_LIBRARIES(add ${add_LIBRARY})
              ENDIF(add_FOUND)
          \end{cmake}
          为了能够让工程找到 Findadd.cmake 模块(存放在工程中的 cmake 目录)我们在主工程文件 CMakeLists.txt 中加入:
          \mint{cmake}{SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)}
    \item 使用自定义的 Findadd 模块构建工程
          仍然采用外部编译的方式,建立 build 目录,进入目录运行:
          \bashinline{cmake ..}
          我们可以从输出中看到:
          \mint{bash}{Found add: /usr/lib/libadd.so}
          如果我们把上面的 FIND\_PACKAGE(add)修改为 FIND\_PACKAGE(add QUIET),则不会看到上面的输出。接下来就可以使用 make 命令构建工程,运行:
          \mint{bash}{./src/add} 可以得到输出
          \mint{bash}{add World}
          说明工程成功构建。
    \item 如果没有找到 add library 呢?
          我们可以尝试将/usr/lib/libadd.x 移动到/tmp 目录,这样,按照 Findadd 模块的定义,就找不到 add library 了,我们再来看一下构建结果:
          \mint{bash}{cmake ..}
          仍然可以成功进行构建,但是这时候是没有办法编译的。
          修改 \bashinline{FIND_PACKAGE(add)}为 \bashinline{FIND_PACKAGE(add REQUIRED)},将 add library 定义为工程必须的共享库。这时候再次运行
          \mint{bash}{cmake ..}
          我们得到如下输出:
          \mint{bash}{CMake Error: Could not find add library}
          因为找不到 libadd.x,所以,整个 Makefile 生成过程被出错中止。
\end{enumerate}
小结:在本节中,我们学习了如何使用系统提供的 Find<NAME>模块并学习了自己编写Find<NAME>模块以及如何在工程中使用这些模块。后面的章节,我们会逐渐学习更多的 cmake 模块使用方法以及用 cmake 来管理 GTK 和 QT4工程。
\subsection{CMake例子}
\begin{bashcode}
    step1
    ├── CMakeLists.txt
    ├── MathFunctions
    │   ├── MathFunctions.h
    │   └── mysqrt.cxx
    ├── TutorialConfig.h.in
    └── tutorial.cxx
\end{bashcode}
CMakeLists.txt文件内容如下：
\cmakefile{code/cmake/step1/CMakeLists.txt}
自定义MathFunctions.h的头文件：
\cppfile{code/cmake/step1/MathFunctions/MathFunctions.h}
函数内容：
\cppfile{code/cmake/step1/MathFunctions/mysqrt.cxx}
主文件内容：
\cppfile{code/cmake/step1/tutorial.cxx}
\begin{example}{实现根据需要使用不同的函数}
    通过多个函数实现根据需要选择指定的函数。
\end{example}
在软件开发过程中，我们需要提供软件的版本号相关信息，CMake支持传入版本号给源文件。
\begin{bash}
    ├── CMakeLists.txt
    ├── MathFunctions
    │   ├── CMakeLists.txt
    │   ├── MathFunctions.h
    │   └── mysqrt.cxx
    ├── TutorialConfig.h.in
    └── tutorial.cxx
\end{bash}
主CMakeLists.txt文件内容：
\cmakefile{code/cmake/step2/CMakeLists.txt}
TutorialConfig.h.in文件内容如下：
\cppfile{code/cmake/step2/TutorialConfig.h.in}
当CMake配置此头文件时，\bashinline{@tutorial version major@}和\bashinline{@tutorial version minor@}的值将被替换。下面就是修改tutorial.cxx代码打印版本号。
\cppfile[firstline=9,lastline=12]{code/cmake/step2/tutorial.cxx}
mysqrt.cxx内容如下：
\cppfile{code/cmake/step2/MathFunctions/mysqrt.cxx}
\begin{example}{cmake代码结构优化}
\end{example}
\begin{bash}
    ├── CMakeLists.txt
    ├── MathFunctions
    │   ├── CMakeLists.txt
    │   ├── MathFunctions.h
    │   └── mysqrt.cxx
    ├── TutorialConfig.h.in
    └── tutorial.cxx
\end{bash}
为了使用c++11的\cppinline{std::stod}方法，需要让工程支持C++11。CMake支持C++11的简单方式是在CMakeLists中添加：
\cmakefile[firstline=7,lastline=8]{code/cmake/step3/CMakeLists.txt}
构建可选库，设置CMakeLists.txt将MathFunctions库设置为可选。
\begin{cmake}
    option(USE_MYMATH "Use tutorial provided math implementation" ON)

    # configure a header file to pass some of the CMake settings
    # to the source code
    configure_file(TutorialConfig.h.in TutorialConfig.h)
\end{cmake}
通过修改USE\_MYMATH的值ON为OFF关闭此库的构建配置，然后修改对于此库的处理代码实现对此库的处理：
\cmakefile[firstline=18,lastline=32]{code/cmake/step2/CMakeLists.txt}
使用变量 EXTRA\_LIBS 收集任何可选库，以便稍后链接到可执行文件中。 变量 EXTRA\_INCLUDES 同样用于可选的头文件。 在处理许多可选组件时，这是一种经典的方法，我们将在下一步中介绍现代方法。tutorial.cxx代码通过：
\cppfile[firstline=27,lastline=31]{code/cmake/step3/tutorial.cxx}
控制使用哪个函数。因为当前源代码需要使用USE\_MYMATH所以需要添加：\cppfile[firstline=4,lastline=4]{code/cmake/step3/TutorialConfig.h.in}
到TutorialConfig.h.in后面。
我们首先声明，链接到 MathFunctions 的任何人都需要包含当前源目录，而 MathFunctions 本身不需要。 所以这可以成为 INTERFACE 的使用需求。
\cmakefile[firstline=3,lastline=7]{code/cmake/step3/MathFunctions/CMakeLists.txt}

\begin{example}{安装软件}
    安装软件到指定的目录，通常我们需要对MathFunctions安装库和头文件，对于应用程序，我们需要安装可执行文件和配置头文件
\end{example}
\begin{bash}
    ├── CMakeLists.txt
    ├── MathFunctions
    │   ├── CMakeLists.txt
    │   ├── MakeTable.cxx
    │   ├── MathFunctions.h
    │   └── mysqrt.cxx
    ├── TutorialConfig.h.in
    └── tutorial.cxx
\end{bash}
CMakeLists.txt文件
\cmakefile[firstline=10,lastline=11]{code/cmake/step4/MathFunctions/CMakeLists.txt}，然后主CMakeLists.txt文件需要添加：
\cmakefile[firstline=33,lastline=37]{code/cmake/step4/CMakeLists.txt}CMake3.15之后可以使用cmake --install安装，--prefix指定安装目录。
\subsubsection{开启单元测试}
\begin{bash}
    ├── CMakeLists.txt
    ├── MathFunctions
    │   ├── CMakeLists.txt
    │   ├── MakeTable.cxx
    │   ├── MathFunctions.h
    │   └── mysqrt.cxx
    ├── TutorialConfig.h.in
    └── tutorial.cxx
\end{bash}
需要在CMakeLists.txt中添加\cmakeinline{enable_testing()}开启单元测试。
\cmakefile[firstline=40,lastline=66]{code/cmake/step4/CMakeLists.txt}

第一步的测试简单的验证应用运行没有段错误或者其它重大错误。这时CTest的基本测试。下一步是使用\bashinline{PASS_REGULAR_EXPRESSION}测试验证输出包含指定的字符串。验证提供不正确的参数的输出提示信息。最后我们调用do\_test，另一个测试通过名字、输入、期待结果测试。重新构建应用然后进入二进制目录运行\bashinline{ctest -N}和\bashinline{ctest -VV}。在调试模式下运行。
\subsubsection{添加系统自检}
让我们考虑向项目中添加一些代码，这些代码依赖于目标平台可能没有的特性。 对于本例，我们将添加一些代码，这取决于目标平台是否具有log和exp 函数。 当然，几乎每个平台都有这些功能，但本教程假定它们并不常见。如果平台有 log 和 exp，那么我们将使用它们计算 mysqrt 函数中的平方根。 我们首先使用工程主 CMakeLists.txt 中的 CheckSymbolExists 模块测试这些函数的可用性。 我们将在 tutorialconfig.h.in 文件中使用新的定义，所以一定要在配置文件之前设置它们。
\cmakefile[firstline=9,lastline=13]{code/cmake/step5/CMakeLists.txt}
现在让我们把这些定义添加到 tutorialconfig.h.in 文件中，这样我们就可以从 mysqrt.cxx 中使用它们:
\cmakefile{code/cmake/step5/MathFunctions/CMakeLists.txt}
修改mysqrt.cxx以包含cmath，接下来，在 mysqrt 函数中的同一个文件中，我们可以提供一个基于 log 和 exp 的替代实现，如果系统上可以使用以下代码(在返回结果之前不要忘记 \# endif!) :
\cppfile[firstline=13,lastline=19]{code/cmake/step5/MathFunctions/mysqrt.cxx}
注意到，我们没有使用 log 和 exp，即使我们认为它们应该可用。 我们应该很快意识到我们忘记了在 mysqrt.cxx 中包含 TutorialConfig.h。我们还需更新MathFunctions/CMakeLists.txt文件让mysql.cxx知道文件为止：
\cmakefile[firstline=22,lastline=25]{code/cmake/step6/MathFunctions/CMakeLists.txt}
在进行此更新之后，继续构建项目并运行构建的 Tutorial 可执行文件。 如果仍然没有使用 log 和 exp，请从构建目录打开生成的 TutorialConfig.h 文件。 也许他们在现有的系统上不可用？
\subsubsection{指定编译定义}
除了 TutorialConfig.h 之外，还有更好的地方保存 HAVE log 和 HAVE exp 值吗？ 让我们尝试使用目标编译定义。首先，从 tutorialconfig.h.in 中删除定义。 我们不再需要在 mysqrt.cxx 中包含 TutorialConfig.h，或者在 mathfunctions / CMakeLists.txt 中包含额外的 include。接下来，我们可以将对 HAVE log 和 HAVE exp 的检查移动到 mathfunctions / CMakeLists.txt，然后将这些值指定为 PRIVATE 编译定义。
%\cmakefile[firstline=10,lastline=18](code/cmake/step5/MathFunctions/CMakeLists.txt)
完成这些更新之后，继续构建项目。 运行生成的 Tutorial 可执行文件，并验证结果是否与本步骤前面的内容相同。首先，让我们删除 MathFunctions / CMakeLists.txt 中的日志和 exp 函数检查。 然后从 mysqrt.cxx 中删除对 HAVE\_LOG 和 HAVE\_EXP 的检查。 同时，我们可以删除 \# include cmath。在 MathFunctions 子目录中，提供了一个名为 MakeTable.cxx 的新源文件来生成表。检查文件之后，我们可以看到表是作为有效的 c + + 代码生成的，输出文件名是作为参数传入的。下一步是向 MathFunctions / CMakeLists.txt 文件添加适当的命令，以生成可执行的 makefile，然后将其作为生成过程的一部分运行。 这需要一些命令来完成。首先，在 MathFunctions / CMakeLists.txt 的顶部，将 MakeTable 的可执行文件作为任何其他可执行文件添加。
\mint{cmake}{add_executable(MakeTable MakeTable.cxx)}
然后我们添加一个自定义命令，指定如何通过运行 MakeTable 生成 Table.h。
%\cmakefile[firstline=20,lastline=24]{code/cmake/step9/MathFunctions/CMakeLists.txt}
接下来，我们必须让 CMake 知道 mysqrt.cxx 依赖于生成的 Table.h 文件。 这是通过将生成的 Table.h 添加到库 MathFunctions 的源列表来实现的。
%\cmakefile[firstline=12,lastline=16](code/cmake/step8/MathFunctions/CMakeLists.txt)
我们还必须将当前的二进制目录添加到 include 目录列表中，以便 mysqrt.cxx 能够找到 Table.h 并将其包含进来。
%\cmakefile[firstline=20,lastline=30](code/cmake/step8/MathFunctions/CMakeLists.txt)
现在，让我们使用生成的表。

接下来假设我们想要将我们的项目分发给其他人，以便他们能够使用它。 我们希望在各种平台上提供二进制文件和源代码发行版。 这与我们在前面的安装和测试(步骤4)中所做的安装稍有不同，在那里我们安装了从源代码构建的二进制文件。 在本例中，我们将构建支持二进制安装和包管理特性的安装包。 为了实现这一点，我们将使用 CPack 创建特定于平台的安装程序。 具体来说，我们需要在顶级 CMakeLists.txt 文件的底部添加几行代码。
\cmakefile[firstline=69,lastline=73]{code/cmake/step9/CMakeLists.txt}
这就是全部。 我们从包含 InstallRequiredSystemLibraries 开始。 此模块将包括当前平台项目所需的任何运行时库。 接下来，我们将一些 CPack 变量设置为存储该项目的许可证和版本信息的位置。 版本信息是在本教程的前面设置的，并且 license.txt 已经包含在本步骤的顶级源目录中。最后，我们将包括 CPack 模块，该模块将使用这些变量和当前系统的一些其他属性来设置安装程序。下一步是以通常的方式构建项目，然后在其上运行 CPack。 要构建二进制发行版，从二进制目录运行:\mint{bash}{cpack}若要指定generator，请使用 -g 选项。 对于 multi-config build，使用-c 指定配置。 例如:
\mint{bash}{cpack -G ZIP -C Debug}
要创建一个源代码发行版，你需要输入:
\mint{bash}{cpack --config CPackSourceConfig.cmake}
或者，在 IDE 中运行 make Package 或右键单击 Package 目标和 Build Project。
\subsubsection{添加Dashboard支持}
添加对向仪表板提交测试结果的支持非常简单。 我们已经在测试支持中为我们的项目定义了一些测试。 现在我们只需要运行这些测试并将它们提交到仪表板。 为了包含对仪表板的支持，我们在顶级 CMakeLists.txt 中包含了 CTest 模块。替换：
\mint{cmake}{
    # enable testing
    enable_testing()
}
为：
\mint{cmake}{
    # enable dashboard scripting
    include(CTest)
}
Ctest 模块将自动调用 enable testing () ，因此我们可以从 CMake 文件中删除它。
我们还需要在顶级目录中创建一个 CTestConfig.cmake 文件，在该文件中我们可以指定项目的名称以及提交仪表板的位置。
\cmakefile{code/cmake/step9/CTestConfig.cmake}
运行时，CTest 将读取这个文件。 要创建一个简单的仪表板，您可以运行 cmake 或 cmake-gui 来配置项目，但是还不要构建它。 相反，将目录更改为二叉树，然后运行:
\mint{cmake}{ctest [-VV] -D Experimental}
记住，对于多配置生成器(例如 visualstudio) ，必须指定配置类型:
\mint{cmake}{ctest [-VV] -C Debug -D Experimental}
或者，从 IDE 构建 Experimental 目标。Ctest 将构建和测试项目，并将结果提交给 Kitware 公共指示板。 你的仪表盘的结果会上传到 Kitware 的公共仪表盘上: \href{链接地址}{https://my.cdash.org/index.php?project=cmaketutorial}。
\subsubsection{静态和共享库的混合}
在本节中，我们将展示如何通过使用 BUILD SHARED libs 变量来控制 add 库的默认行为，并允许控制如何构建没有显式类型(STATIC、 SHARED、 MODULE 或 OBJECT)的库。

为了实现这一点，我们需要将 BUILD shared libs 添加到顶级 CMakeLists.txt 中。 我们使用选项命令，因为它允许用户选择值是开还是关。

接下来，我们将重构 MathFunctions，使之成为一个真正的库，它使用 mysqrt 或 sqrt 封装，而不需要调用代码来执行这个逻辑。 这也意味着 USE mymath 将不会控制构建数学函数，而是控制该库的行为。
\begin{cmake}
    cmake_minimum_required(VERSION 3.10)

    # set the project name and version
    project(Tutorial VERSION 1.0)

    # specify the C++ standard
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_STANDARD_REQUIRED True)

    # control where the static and shared libraries are built so that on windows
    # we don't need to tinker with the path to run the executable
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
        set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")

    option(BUILD_SHARED_LIBS "Build using shared libraries" ON)

    # configure a header file to pass the version number only
    configure_file(TutorialConfig.h.in TutorialConfig.h)

    # add the MathFunctions library
    add_subdirectory(MathFunctions)

    # add the executable
    add_executable(Tutorial tutorial.cxx)
    target_link_libraries(Tutorial PUBLIC MathFunctions)
\end{cmake}
既然我们已经使 MathFunctions 始终被使用，那么我们将需要更新该库的逻辑。 因此，在 mathfunctions / cmakelists.txt 中，我们需要创建一个 SqrtLibrary，在启用 USE mymath 时有条件地构建它。 现在，由于这是一个教程，我们将明确要求 SqrtLibrary 是静态构建的。最终的结果是 mathfunctions / CMakeLists.txt 应该是这样的：
\begin{cmake}
    # add the library that runs
    add_library(MathFunctions MathFunctions.cxx)

    # state that anybody linking to us needs to include the current source dir
    # to find MathFunctions.h, while we don't.
    target_include_directories(MathFunctions
    INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
        )

        # should we use our own math functions
        option(USE_MYMATH "Use tutorial provided math implementation" ON)
        if(USE_MYMATH)

        target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")

        # first we add the executable that generates the table
        add_executable(MakeTable MakeTable.cxx)

        # add the command to generate the source code
        add_custom_command(
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
    COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
        DEPENDS MakeTable
        )

        # library that just does sqrt
        add_library(SqrtLibrary STATIC
        mysqrt.cxx
    ${CMAKE_CURRENT_BINARY_DIR}/Table.h
    )

    # state that we depend on our binary dir to find Table.h
    target_include_directories(SqrtLibrary PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}
    )

    target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
    endif()

    # define the symbol stating we are using the declspec(dllexport) when
    # building on windows
    target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")

    # install rules
    install(TARGETS MathFunctions DESTINATION lib)
    install(FILES MathFunctions.h DESTINATION include)
\end{cmake}
接下来，更新 mathfunctions / mysqrt。 使用 mathfunctions 和细节名称空间:
\cppfile{code/cmake/step9/MathFunctions/mysqrt.cxx}
我们还需要对 tutorial.cxx 做一些修改，这样它就不再使用 USE\_MYMATH:
\begin{enumerate}
    \item 总是包括MathFunctions.h
    \item 永远使用mathfunctions::sqrt
    \item 不要包括 cmath
\end{enumerate}
后，将 mathfunctions / mathfunctions.h 更新为使用 dll export 定义:
\begin{cpp}
    #if defined(_WIN32)
    #  if defined(EXPORTING_MYMATH)
    #    define DECLSPEC __declspec(dllexport)
    #  else
    #    define DECLSPEC __declspec(dllimport)
    #  endif
    #else // non windows
    #  define DECLSPEC
    #endif

    namespace mathfunctions {
            double DECLSPEC sqrt(double x);
        }
\end{cpp}
在这一点上，如果你构建所有的代码，你会注意到链接失败，因为我们将一个没有位置独立代码的静态库和一个有位置独立代码的库组合在一起。 解决这个问题的方法是将 sqllibrary 的 POSITION independent code 目标属性显式设置为 True，无论构建类型如何。
\subsubsection{添加generator表达式}
在生成系统生成期间计算生成器表达式，以生成特定于每个生成配置的信息。在许多目标属性的上下文中都允许使用生成器表达式，例如 LINK libraries、 INCLUDE directories、 COMPILE definition 等。 当使用命令来填充这些属性时，也可以使用它们，例如目标链接库()、目标包括目录()、目标编译定义()等。生成器表达式可用于启用条件链接、编译时使用的条件定义、条件包括目录等。 条件可能基于构建配置、目标属性、平台信息或任何其他可查询信息。有不同类型的生成器表达式，包括逻辑表达式、信息表达式和输出表达式。逻辑表达式用于创建条件输出。 基本表达式是0和1表达式。 \$0: ... 导致空字符串，1: ... 导致“ ... ”的内容。 它们也可以嵌套。生成器表达式的一个常见用法是有条件地添加编译器标志，例如用于语言级别或警告的标志。 一个很好的模式是将这些信息关联到 INTERFACE 目标，允许这些信息传播。 让我们首先构造一个 INTERFACE 目标，并指定所需的 c + + 标准级别为11，而不是使用 CMAKE cxx 标准。所以下面的代码是:
% \begin{cmake}
% # specify the C++ standard
% set(CMAKE_CXX_STANDARD 11)
% set(CMAKE_CXX_STANDARD_REQUIRED True)
% \end{cmake}
将被替换为：
\begin{cmake}
    add_library(tutorial_compiler_flags INTERFACE)
    target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
\end{cmake}
接下来，我们为项目添加所需的编译器警告标志。 由于警告标志根据编译器的不同而不同，我们使用 COMPILE lang 和 id 生成器表达式来控制给定的语言和一组编译器 id 应用哪些标志，如下所示:
\begin{cmake}
    set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
        set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
    target_compile_options(tutorial_compiler_flags INTERFACE
    "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
        "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
    )
\end{cmake}
看看这个，我们可以看到警告标志封装在一个 BUILD interface 条件中。 这样做是为了使已安装项目的使用者不会继承我们的警告标志。
\subsubsection{添加导出配置}
在本教程的安装和测试(步骤4)中，我们添加了让 CMake 安装项目的库和头的功能。 在构建安装程序期间(步骤7) ，我们添加了打包这些信息的能力，这样它就可以分发给其他人。下一步是添加必要的信息，以便其他 CMake 项目可以使用我们的项目，无论是从构建目录、本地安装还是打包时。第一步是更新我们的安装(target)命令，不仅指定 DESTINATION，还指定 EXPORT。 Export 关键字生成并安装一个包含代码的 CMake 文件，以便从安装树中导入 install 命令中列出的所有目标。 因此，让我们继续，通过更新 MathFunctions / cmakelists.txt 中的 install 命令来显式导出 MathFunctions 库:
\begin{cmake}
    install(TARGETS MathFunctions tutorial_compiler_flags
    DESTINATION lib
    EXPORT MathFunctionsTargets)
    install(FILES MathFunctions.h DESTINATION include)
\end{cmake}
现在已经导出了 MathFunctions，我们还需要显式地安装生成的 MathFunctionsTargets.cmake 文件。 这是通过在顶级 CMakeLists.txt 的底部添加以下内容来实现的:
\begin{cmake}
    install(EXPORT MathFunctionsTargets
    FILE MathFunctionsTargets.cmake
    DESTINATION lib/cmake/MathFunctions
    )
\end{cmake}
此时，您应该尝试运行 CMake。 如果一切都设置正确，你会看到 CMake 将生成一个错误，如下所示:
\begin{bash}
    Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
    path:

    "/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"

    which is prefixed in the source directory.
\end{bash}
Cmake 试图说明的是，在生成导出信息的过程中，它将导出一个与当前计算机本质上绑定的路径，这个路径在其他计算机上无效。 解决这个问题的办法是更新 MathFunctions 目标包含目录，以了解在构建目录和安装 / 包中使用它时需要不同的 INTERFACE 位置。 这意味着将目标 include 目录调用 MathFunctions 的方式转换为:
\begin{cmake}
    target_include_directories(MathFunctions
    INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
    $<INSTALL_INTERFACE:include>
    )
\end{cmake}
更新完毕后，我们可以重新运行 CMake 并验证它不再发出警告。现在，我们已经使 CMake 正确地打包了所需的目标信息，但是我们仍然需要生成一个 mathfunctionsconfig.CMake，以便 CMake find package 命令可以找到我们的项目。 因此，让我们继续向项目的顶层添加一个新文件 config.cmakein，内容如下:
\begin{cmake}
    @PACKAGE_INIT@

    include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
\end{cmake}
然后，要正确配置和安装该文件，请在顶级 CMakeLists.txt 的底部添加以下内容:
\begin{cmake}
    install(EXPORT MathFunctionsTargets
    FILE MathFunctionsTargets.cmake
    DESTINATION lib/cmake/MathFunctions
    )

    include(CMakePackageConfigHelpers)
    # generate the config file that is includes the exports
    configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
        "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
    INSTALL_DESTINATION "lib/cmake/example"
    NO_SET_AND_CHECK_MACRO
    NO_CHECK_REQUIRED_COMPONENTS_MACRO
    )
    # generate the version file for the config file
    write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
        VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
        COMPATIBILITY AnyNewerVersion
        )

        # install the configuration file
        install(FILES
    ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
    DESTINATION lib/cmake/MathFunctions
    )
\end{cmake}
现在，我们已经为我们的项目生成了一个可重定位的 CMake 配置，它可以在项目安装或打包之后使用。 如果我们希望我们的项目也可以在构建目录中使用，我们只需要在顶层的 CMakeLists.txt 底部添加以下内容:
\begin{cmake}
    export(EXPORT MathFunctionsTargets
    FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
    )
\end{cmake}
通过这个导出调用，我们现在生成一个 Targets.cmake，允许构建目录中配置的 MathFunctionsConfig.cmake 供其他项目使用，而不需要安装它。
\subsubsection{封装调试和发布}
默认情况下，CMake 的模型是构建目录只包含一个配置，无论是 Debug、 Release、 MinSizeRel 还是 RelWithDebInfo。但是，可以设置 CPack 来同时捆绑多个构建目录，以构建包含同一项目的多个配置的包。首先，我们需要构建一个名为 multi config 的目录，它将包含我们想要打包在一起的所有构建。其次，在 multi config 下面创建一个 debug 和 release 目录。 最后你应该有一个如下的布局:
\begin{bash}
    ─ multi_config
    ├── debug
    └── release
\end{bash}
现在我们需要设置调试版和发布版，这大致需要以下步骤:
\begin{bash}
    cd debug
    cmake -DCMAKE_BUILD_TYPE=Debug ../../MultiPackage/
    cmake --build .
    cd ../release
    cmake -DCMAKE_BUILD_TYPE=Release ../../MultiPackage/
    cmake --build .
    cd ..
\end{bash}
既然调试和发布版本都已完成，我们可以使用自定义的 MultiCPackConfig.cmake 文件将两个版本打包到一个发布版本中。
\mint{cmake}{cpack --config ../../MultiPackage/MultiCPackConfig.cmake}




\subsection{单元测试}
单元测试使用的是BOOST单元测试套件,这个例子主要实现两个数求和的函数。include目录包含了求和函数的头文件add.h。src目录包含了add.cpp函数实现和主代码main.cc。test目录下包含了测试代码test.cpp。

文件源代码内容如下：
\cppfile{code/ctest/ex2/include/add.h}
\cppfile{code/ctest/ex2/src/add.cpp}
\cppfile{code/ctest/ex2/src/main.cc}
\cppfile{code/ctest/ex2/test/test.cpp}
主工程下CMakeLists.txt文件内容如下：
\cmakefile{code/ctest/ex2/CMakeLists.txt}
test目录下的CMakeLists.txt文件内容如下：
\cmakefile{code/ctest/ex2/test/CMakeLists.txt}
